package com.xiaoxiao.spatialite;

import android.database.Cursor;

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.MapElement;
import org.oscim.core.MercatorProjection;
import org.oscim.core.Tag;
import org.oscim.core.Tile;
import org.oscim.layers.tile.MapTile;
import org.oscim.tiling.ITileDataSink;
import org.oscim.tiling.ITileDataSource;
import org.oscim.tiling.TileSource;
import org.spatialite.database.SQLiteDatabase;

import static org.oscim.tiling.QueryResult.FAILED;
import static org.oscim.tiling.QueryResult.SUCCESS;

public class SpatialiteTileDataSource implements ITileDataSource {
    private TileSource tileSource;
    private SQLiteDatabase spatialDb;

    private String[] queryPOIColumn={"display_style","display_text","AsText(geometry) as geometry"};

    private boolean isCancel=false;

    private MapElement mapElement;
    private double mTileY, mTileX, mTileScale;
    private TileProjection mTileProjection;


    public SpatialiteTileDataSource(TileSource tileSource, SQLiteDatabase spatialDb) {
        this.tileSource = tileSource;
        this.spatialDb = spatialDb;
        this.mapElement = new MapElement();
        this.mapElement.layer = 5;
        this.mTileProjection = new TileProjection();
    }

    @Override
    public void query(MapTile tile, ITileDataSink sink) {
        // 根据tile查询数据
        if (spatialDb==null){
            sink.completed(FAILED);
            return;
        }

        // 处理查询到的POI数据，组成MapElement
        processBlocks(tile, sink);

        sink.completed(SUCCESS);
    }

    /**
     * Map rendering.
     */
    private void processBlocks(MapTile tile, ITileDataSink sink){
        // 查询渲染POI
        processPois(tile, sink);
    }

    private void processPois(MapTile tile, ITileDataSink mapDataSink){
        if (tile.zoomLevel>=16){ // 17级以上才需要查询，否则资源消耗过大
            mTileProjection.setTile(tile);

            mTileScale = 1 << tile.zoomLevel;
            mTileX = tile.tileX / mTileScale;
            mTileY = tile.tileY / mTileScale;
            mTileScale *= Tile.SIZE;

            StringBuilder querySelectionSB=new StringBuilder("Contains(")
                    .append("BuildMbr(")
                    .append(tile.getBoundingBox().getMinLongitude()).append(",")
                    .append(tile.getBoundingBox().getMinLatitude()).append(",")
                    .append(tile.getBoundingBox().getMaxLongitude()).append(",")
                    .append(tile.getBoundingBox().getMaxLatitude())
                    .append(")")
                    .append(",").append("geometry").append(")");
//            querySelectionSB = new StringBuilder("Contains(BuildMbr(116.395639,39.805522,116.411132,39.939736),geometry)");
            Cursor cursor=spatialDb.query("edit_pois",queryPOIColumn, querySelectionSB.toString(), null, null, null, null);
            if (cursor!=null){
                Tag layerTag = new Tag("layer", "fastmap-poi");
                while (!cursor.isClosed()&&cursor.moveToNext()/*&&!isCancel*//*如果此处已经设置为取消，则无需继续读取数据库数据以及渲染*/){
                    mapElement.clear();
                    mapElement.tags.clear();
                    if (layerTag != null)
                        mapElement.tags.add(layerTag);
                    System.out.println(cursor.getString(1)+"--------->"+cursor.getString(2));
                    // 添加名称tag
                    mapElement.tags.add(new Tag("name", cursor.getString(cursor.getColumnIndex("display_text"))));
                    mapElement.tags.add(new Tag("fastmap-style", cursor.getString(cursor.getColumnIndex("display_style"))));
                    // 添加显示位置
                    String geometry=cursor.getString(2);
                    if (geometry!=null&&geometry.startsWith("POINT")) {
                        WKTReader wktReader=new WKTReader();
                        try {
                            Geometry geoPoint=wktReader.read(geometry);
//                            mTileProjection.projectPoint(((int)(point.getX()*1000000)),(int)(point.getY()*1000000),mapElement);
                            mapElement.startPoints();
//
                            mapElement.addPoint(MercatorProjection.longitudeToTileX(geoPoint.getCoordinate().x, tile.zoomLevel), MercatorProjection.latitudeToTileY(geoPoint.getCoordinate().y, tile.zoomLevel));
                            //add tag information
                            //tileSource.decodeTags(mMapElement, mTagMap);
                            if (mapElement.tags.size() == 0)
                                return;

                            //tileSource.postGeomHook(mapElement);

                            if (mapElement.type == GeometryBuffer.GeometryType.NONE)
                                return;

                            //process this element
                            mapDataSink.process(mapElement);
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }
                }
                cursor.close();
            }
        }
    }

    @Override
    public void dispose() {
        if (spatialDb!=null){
            spatialDb.close();
            return;
        }
    }

    @Override
    public void cancel() {
        isCancel = true;
    }

    static class TileProjection {
        private static final double COORD_SCALE = 1000000.0;

        long dx, dy;
        double divx, divy;

        void setTile(Tile tile) {
            /* tile position in pixels at tile zoom */
            long x = tile.tileX * Tile.SIZE;
            long y = tile.tileY * Tile.SIZE + Tile.SIZE;

            /* size of the map in pixel at tile zoom */
            long mapExtents = Tile.SIZE << tile.zoomLevel;

            /* offset relative to lat/lon == 0 */
            dx = (x - (mapExtents >> 1));
            dy = (y - (mapExtents >> 1));

            /* scales longitude(1e6) to map-pixel */
            divx = (180.0 * COORD_SCALE) / (mapExtents >> 1);

            /* scale latitude to map-pixel */
            divy = (Math.PI * 2.0) / (mapExtents >> 1);
        }

        public void projectPoint(int lat, int lon, MapElement out) {
            out.clear();
            out.startPoints();
            out.addPoint(projectLon(lon), projectLat(lat));
        }

        public float projectLat(double lat) {
            double s = Math.sin(lat * ((Math.PI / 180) / COORD_SCALE));
            double r = Math.log((1.0 + s) / (1.0 - s));

            return Tile.SIZE - (float) (r / divy + dy);
        }

        public float projectLon(double lon) {
            return (float) (lon / divx - dx);
        }

        void project(MapElement e) {

            float[] coords = e.points;
            int[] indices = e.index;

            int inPos = 0;
            int outPos = 0;

            boolean isPoly = e.isPoly();

            for (int idx = 0, m = indices.length; idx < m; idx++) {
                int len = indices[idx];
                if (len == 0)
                    continue;
                if (len < 0)
                    break;

                float lat, lon, pLon = 0, pLat = 0;
                int cnt = 0, first = outPos;

                for (int end = inPos + len; inPos < end; inPos += 2) {
                    lon = projectLon(coords[inPos]);
                    lat = projectLat(coords[inPos + 1]);

                    if (cnt != 0) {
                        /* drop small distance intermediate nodes */
                        if (lat == pLat && lon == pLon) {
                            //log.debug("drop zero delta ");
                            continue;
                        }
                    }
                    coords[outPos++] = pLon = lon;
                    coords[outPos++] = pLat = lat;
                    cnt += 2;
                }

                if (isPoly && coords[first] == pLon && coords[first + 1] == pLat) {
                    /* remove identical start/end point */
                    //log.debug("drop closing point {}", e);
                    indices[idx] = (short) (cnt - 2);
                    outPos -= 2;
                } else {
                    indices[idx] = (short) cnt;
                }
            }
            if (e.labelPosition != null) {
                e.labelPosition.x = projectLon(e.labelPosition.x);
                e.labelPosition.y = projectLat(e.labelPosition.y);
            }
            if (e.centroidPosition != null) {
                e.centroidPosition.x = projectLon(e.centroidPosition.x);
                e.centroidPosition.y = projectLat(e.centroidPosition.y);
            }
        }
    }
}
